package rabbit.flt.plugins.common;

import rabbit.flt.common.context.TraceContext;
import rabbit.flt.common.log.AgentLoggerFactory;
import rabbit.flt.common.log.Logger;
import rabbit.flt.common.trace.TraceData;
import rabbit.flt.common.trace.io.HttpRequest;
import rabbit.flt.common.utils.CollectionUtils;
import rabbit.flt.common.utils.PathParser;
import rabbit.flt.common.utils.ReflectUtils;
import rabbit.flt.common.utils.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 负责将controller中定义的path填充到trace data中
 */
public class ControllerPathHelper {

    private Logger logger = AgentLoggerFactory.getLogger(getClass());

    private static final ControllerPathHelper inst = new ControllerPathHelper();

    private static List<Class<? extends Annotation>> annotationClasses;

    /**
     * 方法 请求路径 缓存
     */
    private Map<Method, List<String>> methodPathCache = new ConcurrentHashMap<>();

    /**
     * 路径缓存
     */
    private Map<Class, List<String>> classPathCache = new ConcurrentHashMap<>();

    private ControllerPathHelper() {
    }

    /**
     * 填充support trace
     *
     * @param method
     * @param controller
     */
    public static void fillSupportTraceData(Method method, Object controller) {
        if (TraceContext.isWebTraceDataLocked()) {
            return;
        }
        TraceData supportTraceData = TraceContext.getWebTraceData();
        HttpRequest request = supportTraceData.getHttpRequest();
        List<String> paths = getAndCacheMethodPath(method, controller.getClass());
        String baseRequestPath = getBaseRequestPath(controller.getClass());
        String path;
        if (1 == paths.size()) {
            path = paths.get(0);
        } else {
            path = getMatchedPath(baseRequestPath, request, paths);
        }
        path = baseRequestPath.concat(path);
        supportTraceData.setNodeName(path);
        if (null != request) {
            // 兼容web flux
            if (!StringUtils.isEmpty(request.getContextPath()) && !"/".equals(request.getContextPath())) {
                supportTraceData.setNodeName(request.getContextPath().concat(path));
            }
            supportTraceData.setNodeDesc(request.getMethod().concat(": ").concat(supportTraceData.getNodeName()));
        }
        supportTraceData.setHasMappedController(true);
        supportTraceData.setData(method.getDeclaringClass().getName().concat(".").concat(method.getName()));
        TraceContext.lockWebTraceData();
    }

    /**
     * 根据路径进行匹配
     *
     * @param baseRequestPath
     * @param request
     * @param list
     * @return
     */
    private static String getMatchedPath(String baseRequestPath, HttpRequest request, List<String> list) {
        String requestUri = request.getRequestUri();
        String contextPath = "/".equals(request.getContextPath()) ? "" : request.getContextPath();
        for (String path : list) {
            if (PathParser.parsePattern(contextPath + baseRequestPath + path).match(requestUri)) {
                return path;
            }
        }
        return "";
    }

    /**
     * 读取method上的路径信息
     *
     * @param method
     * @param controllerClz
     * @return
     */
    private static List<String> getAndCacheMethodPath(Method method, Class<?> controllerClz) {
        return inst.methodPathCache.computeIfAbsent(method, m -> getMethodPath(method, controllerClz));
    }

    public static List<String> getMethodPath(Method method, Class<?> controllerClz) {
        Annotation annotation = getTargetAnnotation(method);
        if (null == annotation) {
            // 从基类方法找注解
            annotation = getMethodAnnotationFromSupperClz(method, controllerClz);
        }
        return getPathFromAnnotation(annotation);
    }

    private static String getBaseRequestPath(Class<?> controllerClz) {
        Class<Annotation> annClz = (Class<Annotation>) loadClass("org.springframework.web.bind.annotation.RequestMapping");
        List<String> path = getPathFromAnnotation(controllerClz.getAnnotation(annClz));
        return CollectionUtils.isEmpty(path) ? "" : path.get(0);
    }

    /**
     * 读取注解上的path
     *
     * @param annotation
     * @return
     */
    private static List<String> getPathFromAnnotation(Annotation annotation) {
        try {
            if (null == annotation) {
                return new ArrayList<>();
            }
            Method m = annotation.getClass().getDeclaredMethod("value");
            String[] value = (String[]) m.invoke(annotation);
            if (!CollectionUtils.isEmpty(value)) {
                return Arrays.asList(value);
            }
            m = annotation.getClass().getDeclaredMethod("name");
            String name = StringUtils.toString(m.invoke(annotation));
            if (!StringUtils.isEmpty(name)) {
                return Arrays.asList(name);
            }
            m = annotation.getClass().getDeclaredMethod("path");
            Object result = m.invoke(annotation);
            if (null != result) {
                if (result.getClass().isArray()) {
                    String[] paths = (String[]) result;
                    return Arrays.asList(paths);
                } else {
                    String path = StringUtils.toString(result);
                    if (!StringUtils.isEmpty(path)) {
                        return Arrays.asList(path);
                    }
                }
            }
        } catch (Exception e) {
            inst.logger.error(e.getMessage());
        }
        return new ArrayList<>();
    }

    /**
     * 获取基类方法上的注解
     *
     * @param method
     * @param controllerClz
     * @return
     */
    private static Annotation getMethodAnnotationFromSupperClz(Method method, Class<?> controllerClz) {
        List<Type> superInterfaces = new ArrayList<>();
        loadAllSuperInterfaces(superInterfaces, controllerClz.getGenericInterfaces());
        List<Class<? extends Annotation>> targetAnnotations = getAnnotationClasses();
        for (Type type : superInterfaces) {
            for (Class<? extends Annotation> annClz : targetAnnotations) {
                Annotation ann = ReflectUtils.call(() -> {
                    Method declaredMethod = ((Class<?>) type).getDeclaredMethod(method.getName(), method.getParameterTypes());
                    return declaredMethod.getAnnotation(annClz);
                }, e -> null);
                if (null != ann) {
                    return ann;
                }
            }
        }
        return null;
    }

    private static void loadAllSuperInterfaces(List<Type> list, Type[] superInterfaces) {
        for (Type type : superInterfaces) {
            list.add(type);
            loadAllSuperInterfaces(list, ((Class<?>) type).getGenericInterfaces());
        }
    }

    /**
     * 找寻方法注解
     *
     * @param method
     * @return
     */
    private static Annotation getTargetAnnotation(Method method) {
        for (Class<? extends Annotation> annClz : getAnnotationClasses()) {
            Annotation annotation = method.getAnnotation(annClz);
            if (null != annotation) {
                return annotation;
            }
        }
        return null;
    }

    /**
     * 加载spring rest注解类
     *
     * @return
     */
    private static List<Class<? extends Annotation>> getAnnotationClasses() {
        if (null != annotationClasses) {
            return annotationClasses;
        }
        String[] names = new String[]{
                "org.springframework.web.bind.annotation.PostMapping",
                "org.springframework.web.bind.annotation.GetMapping",
                "org.springframework.web.bind.annotation.RequestMapping",
                "org.springframework.web.bind.annotation.DeleteMapping",
                "org.springframework.web.bind.annotation.PutMapping",
                "org.springframework.web.bind.annotation.PatchMapping"
        };
        List<Class<? extends Annotation>> list = new ArrayList<>();
        for (int i = 0; i < names.length; i++) {
            try {
                list.add((Class<? extends Annotation>) loadClass(names[i]));
            } catch (Exception e) {
                inst.logger.error(e.getMessage(), e);
            }
        }
        annotationClasses = list;
        return annotationClasses;
    }

    private static Class<?> loadClass(String name) {
        return ReflectUtils.call(() -> Thread.currentThread().getContextClassLoader().loadClass(name));
    }

    /**
     * 缓存controller的path信息
     *
     * @param controllerClz
     */
    public static void loadControllerPath(Class<?> controllerClz) {
        if (inst.classPathCache.containsKey(controllerClz)) {
            return;
        }
        Class<?> clz = controllerClz;
        List<Method> methods = new ArrayList<>();
        while (true) {
            for (Method method : clz.getDeclaredMethods()) {
                if (method.isSynthetic()) {
                    continue;
                }
                methods.add(method);
            }
            if (Object.class == clz.getSuperclass()) {
                break;
            }
            clz = clz.getSuperclass();
        }
        List<String> pathList = new ArrayList<>();
        String base = getBaseRequestPath(controllerClz);
        for (Method method : methods) {
            getAndCacheMethodPath(method, controllerClz).forEach(path -> pathList.add(base + path));
        }
        inst.classPathCache.put(controllerClz, pathList);
    }

    /**
     * 获取缓存
     *
     * @return
     */
    public static Map<Class, List<String>> getClassPathCache() {
        return inst.classPathCache;
    }
}
